package com.lfy.ch03;


import com.lfy.ch01.Leifengyang;
import com.lfy.ch01.Person;

import java.io.DataOutputStream;
import java.lang.reflect.*;
import java.util.Arrays;

/**
 * 类不会被重复加载（类加载器保证）。
 *      1.类在内存中只有一份（单实例）
 *      2.类的对象有很多，类本身只有一个。
 *      3.synchronized（String.class） 加锁。用的同一把锁。
 *
 * Person p1 = new Person();
 * 1、Person类在系统启动的时候，就会被加载到内存中（方法区）。
 * 2、new Person()： 去方法区拿到了 Person类的字节码内容，然后通过字节码内容中的无参构造器代码创建对象。
 * 3、创建的对象放到堆内存中。 内存地址为 0x12345678
 * 4、把 0x12345678 这个地址赋值给 p1。
 * 5、main方法在main线程中运行。
 *      1)、p1变量在main方法中。每个方法压栈就是压入一个栈帧（stack frame）；main栈中
 *      2)、栈帧中有一个【局部变量表】，有一个p1变量，p1的值是 0x12345678；
 *              引用类型，变量名对应的值就是内存地址
 *      3)、每次new一个线程就是新开一个栈； new Thread() 对象在里面；方法在栈中跑起来
 *
 *
 * 后来面试阶段完整过JVM；
 *
 * 类全名：包名+类名
 *
 *  实现能拿到接口，子能拿到父； 反之不行；
 *  万物动态之源；
 */
public class ReflectTest {

    public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
        // 正射：直接new对象
        Dog dog = new Dog("哈哈","小花",2);
        dog.eat();


        //根据 dog 对象，
        //1、分析他是哪个类创建的实例
        // 1）、根据对象获取到class
        Class<?> aClass = dog.getClass();
        System.out.println(aClass);

        // 2）、根据类获取class
        Class<?> dogClass = Dog.class;
        System.out.println(dogClass);

        // 3）、根据类名获取class；不存在报错：java.lang.ClassNotFoundException
        Class<?> forName = Class.forName("com.lfy.ch03.Dog");
        System.out.println(forName);


        //2、获取类的元信息（类名、全类名、权限、继承？、实现？）
        String name = aClass.getName();
        String canonicalName = aClass.getCanonicalName();
        String packageName = aClass.getPackageName();
        String simpleName = aClass.getSimpleName();
        String typeName = aClass.getTypeName();
        System.out.println(name);
        System.out.println(canonicalName);
        System.out.println(packageName);
        System.out.println(simpleName);
        System.out.println(typeName);


        Class<?> superclass = aClass.getSuperclass();
        aClass.getGenericSuperclass();
        System.out.println("父类:"+superclass);

        Class<?>[] interfaces = aClass.getInterfaces();
        System.out.println("接口:"+interfaces[0]);

        // getGenericXxxx： 获取到带泛型信息的Xxx。
        Type[] genericInterfaces = aClass.getGenericInterfaces();
        System.out.println("泛型接口:"+genericInterfaces[0]);


        //3、修饰符
        int modifiers = aClass.getModifiers();
        System.out.println(modifiers);
        boolean aPublic = Modifier.isPublic(modifiers);
        System.out.println("public: "+aPublic);
//        boolean isPublic = Modifier.isPublic(modifiers);
//        boolean isAbstract = Modifier.isAbstract(modifiers);


        //4、获取到类上标注的注解
        Leifengyang annotation = aClass.getAnnotation(Leifengyang.class);
        //toString() 打印注解信息。
        System.out.println(annotation);

        int age = annotation.age();
        if (age > 1){
            System.out.println("雷丰阳已经老了....");
        }
        // ======== 获取类的元信息； 大括号外面 ==============



        // DataOutputStream stream = new DataOutputStream();
        // 请在文件中记录，DataOutputStream 有多少个方法？ 5
        // lombok
        // ========= 接下来 获取 类 大括号 里面的内容 =============

        // 1、获取到类里面声明了哪些属性
        Field[] fields = aClass.getFields(); //public修饰的属性
        for (Field f : fields){
            System.out.println("属性名：" + f.getName() + " 类型：" + f.getType() + " 修饰符：" + Modifier.toString(f.getModifiers()));
        }
        System.out.println("=============");
        Field[] declaredFields = aClass.getDeclaredFields(); // 所有修饰符的属性(包括私有的)
        for (Field f : declaredFields){
            System.out.println("属性名：" + f.getName() + " 类型：" + f.getType() + " 修饰符：" + Modifier.toString(f.getModifiers()));
        }


        System.out.println("======方法===========");
        //2、获取到类里面声明了哪些方法
        Method[] methods = aClass.getMethods();// public
        for (Method m :methods){
            Parameter[] parameters = m.getParameters();
            Class<?> returnType = m.getReturnType();
            System.out.println("方法名："+m.getName() + "； 参数表:"+Arrays.toString(parameters) + "；返回值类型："+ returnType);
        }
        System.out.println("--------------------");
        Method[] declaredMethods = aClass.getDeclaredMethods();// 所有的
        for (Method m :declaredMethods){
            Parameter[] parameters = m.getParameters();
            Class<?> returnType = m.getReturnType();
            System.out.println("方法名："+m.getName() + "； 参数表:"+Arrays.toString(parameters) + "；返回值类型："+ returnType);
        }



        //3、获取到类里面声明了哪些构造器
        Constructor<?>[] constructors = aClass.getDeclaredConstructors(); // 所有构造器，包含私有
        for (Constructor<?> c : constructors){
            Parameter[] parameters = c.getParameters();
            String name1 = c.getName();
            System.out.println("构造器名字："+name1+"; 参数表："+Arrays.toString(parameters));
        }

//        Mimi mimi = new Mimi();
        //4、暴力破解权限

        System.out.println("=====暴力破解权限======");
        Class<?> mimiClass = Mimi.class;
        Constructor<?>[] constructors1 = mimiClass.getDeclaredConstructors();
        for (Constructor<?> c : constructors1){
            System.out.println(c);

            //设置为true，就可以无视权限了。 比如私有构造器也可以创建对象了。
            c.setAccessible(true);

            //1、利用构造器创建对象; 得到的对象：com.lfy.ch03.Mimi@b1a58a3
            Object o = c.newInstance();
            System.out.println("得到的对象："+o);
        }


        dog.eat();
        //4、Mimi类，创建不了对象，属性还是私有的，没有getter/setter方法，功能方法也是私有


        // ===============反射测试===========================

        //4.1、拿到Mimi类的声明
        Class<Mimi> mimiClass1 = Mimi.class;
        //4.2、拿到构造器创建对象
        Constructor<Mimi> constructor = mimiClass1.getDeclaredConstructor();//精确获取无参构造器
        //4.3、设为可访问，然后创建对象
        constructor.setAccessible(true);
        //4.4、创建对象【属性的修改和获取都是要操作对象，除非是静态属性】
        Mimi mimi = constructor.newInstance(); // 创建对象，但是没有赋值
        Mimi mimi2 = constructor.newInstance(); // 创建对象，但是没有赋值
        Mimi mimi3 = constructor.newInstance(); // 创建对象，但是没有赋值
//        mimi.getMiName();
        System.out.println("秘密对象："+mimi);
        //4.5、给 miName 属性赋值 getDeclaredFields()；获取所有， 不加s就是获取单个；
        Field miNameField = mimiClass1.getDeclaredField("miName");
        miNameField.setAccessible(true); //可访问
        // 给 mimi 对象的 miName 属性 设置为 雷丰阳
        miNameField.set(mimi,"雷丰阳");
        //4.6、获取 miName 属性值； 获取 mimi 对象的 miName 属性值
        Object o = miNameField.get(mimi);
        System.out.println("属性值："+o);

        //4.7、调用私有方法;   get[Declared]Xxxx[s]： Declared获取所有包含私有， s：所有的，不加s单个
        Method dance = mimiClass1.getDeclaredMethod("dance");
        //4.8、设置可访问; public 方法不需要设置可访问。 但是私有方法需要设置可访问。
        dance.setAccessible(true);
        //4.9、调用私有方法；  对象执行方法；执行 mimi 对象的 dance 方法。
        dance.invoke(mimi);


        //学习：【熟练工！！！！！！！】
        //反射：只能子类获取父
        //SPI机制；可以获取到某个类的所有功能实现类。
    }

    public static void abc(String[] args) {
        Person p1 = new Person();

        //创建一万个线程不启动，仅仅代表有一万个线程对象。
        new Thread(() -> {});
        new Thread(() -> {});
        new Thread(() -> {});
        new Thread(() -> {});
        new Thread(() -> {});
        new Thread(() -> {});
        new Thread(() -> {});
    }
}
